package com.fpno2.transaction;

import com.fpno1.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.*;

/*
* 1.什么叫数据库事务
* 事务：一组逻辑操作单元,使数据从一种状态变换到另一种状态。
*   一组逻辑操作单元，一个或多个dml操作
* 2.事务处理的原则：保证所有事务都作为一个工作单元来执行，即使出现了故障，都不能改变这种执行方式。
* 当在一个事务中执行多个操作时，要么所有的事务都**被提交(commit)**，那么这些修改就永久地保存下来；
* 要么数据库管理系统将放弃所作的所有修改，整个事务**回滚(rollback)**到最初状态。
*
* 3.数据一旦提交就不会回滚
*
* 4.数据自动提交的操作
*   ddl(对数据库，数据库表的操作)操作一旦执行，都会自动提交
*       set autocommit=false对ddl操作失效
*   dml(insert delete update)默认情况下，一旦执行，就会提交
*       我们可以通过set autocommit=false的方式取消dml操作的自动提交
*   默认再关闭连接时会自动提交数据
* */
public class TransactionTest {
    /*
    * 针对于数据表user_table来说
    * AA用户给BB用户转账100
    *
    * update user_table set balance=balance-100 where user="AA"
    * update user_table set balance=balance+100 where user="BB"
    * */

    @Test
    public void testUpdate(){

        String sql1="update user_table set balance=balance-100 where user=?";
        update(sql1,"AA");

        //模拟网络异常
        System.out.println(10/0);

        String sql2="update user_table set balance=balance+100 where user=?";
        update(sql2,"BB");

        System.out.println("转账成功");
    }


    //通用的增删改操作---version1.0
    public int update(String sql,Object ...args) {//可变形参的个数就是填充占位符的个数
        //获取数据库的链接
        Connection connection=null;
        PreparedStatement ps=null;
        try {
            connection = JDBCUtils.getConnection();
            //预编译sql语句
            ps = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }
            //执行
            ps.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭资源
            JDBCUtils.closeResource(connection,ps);

        }
        return 0;
    }
//################################考虑数据库事务后的转账操作########################
    @Test
    public void testUpdateWithTx()  {
        Connection connection=null;
        try {
            connection = JDBCUtils.getConnection();
            //System.out.println(connection.getAutoCommit());
            //取消数据的自动提交
            connection.setAutoCommit(false);
            String sql1="update user_table set balance=balance-100 where user=?";
            update(connection,sql1,"AA");

            //模拟网络异常
            //System.out.println(10/0);

            String sql2="update user_table set balance=balance+100 where user=?";
            update(connection,sql2,"BB");

            System.out.println("转账成功");
            connection.commit();
        }catch (Exception e){
            e.printStackTrace();
            //产生异常则回滚数据
            try {
                connection.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }finally {
            //修改其为自动提交数据
            //主要针对使用数据库连接池
            try {
                connection.setAutoCommit(true);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            JDBCUtils.closeResource(connection,null);
        }

    }




    //通用的增删改操作---version2.0,考虑了事务
    public int update(Connection connection,String sql,Object ...args) {//可变形参的个数就是填充占位符的个数
        PreparedStatement ps=null;
        try {
            //1.预编译sql语句
            ps = connection.prepareStatement(sql);
            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }
            //3.执行
            ps.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //4.关闭资源
            JDBCUtils.closeResource(null,ps);

        }
        return 0;
    }

    //#####################################################

    @Test
    public void testTransactionSelect() throws Exception{
        Connection connection = JDBCUtils.getConnection();
        //获取当前事务的隔离级别
        System.out.println(connection.getTransactionIsolation());
        //设置数据库的隔离级别
        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        connection.setAutoCommit(false);
        String sql="select user,password,balance from user_table where user=?";
        User user = getInstance(connection, User.class, sql, "CC");
        System.out.println(user);
    }
    @Test
    public void testTransactionUpdate()throws Exception{
        Connection connection = JDBCUtils.getConnection();
        connection.setAutoCommit(false);
        String sql="update user_table set balance=? where user=?";
        update(connection,sql,5000,"CC");
        Thread.sleep(15000);
        System.out.println("修改结束");
    }

    //通用查询操作，返回一条数据表中的记录（version2.0）
    public<T> T getInstance(Connection connection,Class<T> clazz,String sql,Object ...args) {
        PreparedStatement ps=null;
        ResultSet resultSet=null;
        try {
        ps = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);
            }
            resultSet = ps.executeQuery();
            //获取结果集的元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            //通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();
            if (resultSet.next()){
                T t = clazz.newInstance();
                //处理结果集一行数据中的每一个列
                for (int i=0;i<columnCount;i++){
                    Object columnValue = resultSet.getObject(i + 1);
                    //获取每个列的列名
                    String columnName = metaData.getColumnLabel(i+1);
                    //给customer对象指定的某个属性赋值为columnValue,通过反射
                    Field field = clazz.getDeclaredField(columnName);
                /*当类的某个属性或方法的访问权限为private的时候，可以利用setAccessible方法设置
java的访问权限*/
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }

                return t;
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally{
            JDBCUtils.closeResource(null,ps,resultSet);

        }

        return null;

    }

}
